package tcp

import (
	"ams/dao"
	"ams/global"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/sirupsen/logrus"
	"lib/network"
	"lib/structs/ams"
	"lib/utils"
	"net"
	"sync"
	"time"
)

type Client struct {
	connId     string // 连接码，每次连接都会变化
	uniqueCode string // 设备唯一码，保存在客户端的唯一标识
	clientId   int64  // 设备ID，保存在服务端的唯一标识
	conn       net.Conn
	inCh       chan ams.TcpResponse
	outCh      chan ams.TcpInstruction
	closeCh    chan struct{}
	isClosed   bool
	once       sync.Once
	mutex      sync.Mutex
}

func newClient(conn net.Conn) *Client {
	client := &Client{
		conn:    conn,
		closeCh: make(chan struct{}),
		inCh:    make(chan ams.TcpResponse, 100),
		outCh:   make(chan ams.TcpInstruction, 100),
	}
	return client
}

func (c *Client) handleMsg() {
	for {
		select {
		case <-c.closeCh:
			return
		case in, ok := <-c.inCh:
			if !ok {
				return
			}
			if c.uniqueCode == "" {
				c.uniqueCode = in.UniqueCode
			}
			if _, ok := tcpServer.clientMap.Load(c.uniqueCode); !ok {
				tcpServer.clientMap.Store(c.uniqueCode, c)
			}
			logrus.Debugf("server 收到信息：%s", in.ResCode)
			dao.OnlineMap.Store(c.uniqueCode, time.Now().Unix())
			switch in.ResCode {
			case ams.ResCode.Ping:
				if c.clientId == 0 {
					in1 := ams.TcpInstruction{
						InsCode:    ams.InsCode.HandsUp,
						InsContent: ams.BaseInstructContent.HandsUp,
					}
					c.Writer(in1)
				}
				continue
			case ams.ResCode.GetHandsUpArgus:
				in1 := ams.TcpInstruction{
					InsCode:    ams.InsCode.SetHandsUpArgus,
					InsContent: ams.BaseInstructContent.SetHandsUpArgus,
				}
				c.Writer(in1)
			case ams.ResCode.HandsUp:
				cid, err := handler.HandUp(in)
				if err != nil {
					logrus.Errorf("HandsUp err:[%v],in:[%v]", err, in)
					continue
				}
				c.clientId = cid
				c.Writer(ams.TcpInstruction{
					InsCode:    ams.InsCode.SetId,
					InsContent: fmt.Sprintf("%d", cid),
				})
			case ams.ResCode.GetId:
				m, err := handler.GetClientInfo(in.UniqueCode)
				if err != nil {
					logrus.Errorf("getClientInfo err:[%v],in:[%v]", err, in)
					continue
				}
				c.clientId = m.Id
				c.Writer(ams.TcpInstruction{
					InsCode:    ams.InsCode.SetId,
					InsContent: fmt.Sprintf("%d", m.Id),
				})
			case ams.ResCode.CheckPluginList:
				li, err := handler.checkPluginList(in)
				if err != nil {
					logrus.Errorf("checkPluginList err:[%v],in:[%v]", err, in)
					continue
				}
				if len(li) > 0 {
					c.Writer(ams.TcpInstruction{
						InsCode:    ams.InsCode.ExecutePlugin,
						InsContent: utils.Obj2Str(handler.getPluginUpdateInstruct(li)),
					})
				}
			case ams.ResCode.CheckPolicy:
				li, err := handler.checkPolicyList(in)
				if err != nil {
					logrus.Errorf("checkPolicyList err:[%v],in:[%v]", err, in)
					continue
				}
				if len(li) > 0 {
					c.Writer(ams.TcpInstruction{
						InsCode:    ams.InsCode.UpdatePolicy,
						InsContent: utils.Obj2Str(li),
					})
				}
			case ams.ResCode.UploadLog:
				if c.clientId == 0 {
					continue
				}
				lid, err := handler.uploadCollectLog(in, c.clientId)
				if err != nil {
					logrus.Errorf("uploadCollectLog err:[%v],in:[%v]", err, in)
					continue
				}
				c.Writer(ams.TcpInstruction{
					InsCode:    ams.InsCode.Ack,
					InsContent: utils.Obj2Str(lid),
				})
			case ams.ResCode.ExecutePlugin:
				dao.ResCache.Store(fmt.Sprintf("%d_%s", c.clientId, ams.ResCode.ExecutePlugin), in.ResContent)
			}
		}
	}
}

func (c *Client) Writer(in ams.TcpInstruction) {
	if !c.IsClosed() {
		select {
		case <-c.closeCh:
			return
		case c.outCh <- in:
		}
	}
}

// connInit 连接就绪后的第一个消息,通常是发送 举手参数、守卫参数、文件服务器地址等等
func (c *Client) connInit() {
	// 设置举手的指令
	in1 := ams.TcpInstruction{
		InsCode:    ams.InsCode.SetHandsUpArgus,
		InsContent: ams.BaseInstructContent.SetHandsUpArgus,
	}
	c.Writer(in1)
	// 设置守卫指令
	in2 := ams.TcpInstruction{
		InsCode:    ams.InsCode.SetGuarderArgus,
		InsContent: ams.BaseInstructContent.SetGuarderArgus,
	}
	c.Writer(in2)
	// 设置文件服务器
	in3 := ams.TcpInstruction{
		InsCode:    ams.InsCode.SetFileServerAddr,
		InsContent: global.Conf.FileServer,
	}
	c.Writer(in3)
	// 设置策略
	in4 := ams.TcpInstruction{
		InsCode:    ams.InsCode.UpdatePolicy,
		InsContent: utils.Obj2Str(dao.Policies),
	}
	c.Writer(in4)
}

func (c *Client) readLoop() {
	for {
		select {
		case <-c.closeCh:
			return
		default:
			_ = c.conn.SetReadDeadline(time.Now().Add(time.Duration(ams.TcpDeadline) * time.Second))
			bs, err := network.Decode(c.conn)
			if err != nil {
				var netErr net.Error
				if errors.As(err, &netErr) && netErr.Timeout() {
					continue
				}
				return
			}
			var in ams.TcpResponse
			err = json.Unmarshal(bs, &in)
			if err != nil {
				continue
			}
			if !c.IsClosed() {
				c.inCh <- in
			}
		}
	}
}

func (c *Client) writeLoop() {
	for {
		select {
		case <-c.closeCh:
			return
		case in, ok := <-c.outCh:
			if !ok {
				return
			}
			bs, err := network.Encode(in)
			if err != nil {
				return
			}
			_ = c.conn.SetWriteDeadline(time.Now().Add(time.Duration(ams.TcpDeadline) * time.Second))
			_, err = c.conn.Write(bs)
			if err != nil {
				logrus.Errorf("clientConn writeLoop err [%v],msg [%v],client [%v]", err, in, c)
				c.close()
				return
			}
		}
	}
}
func (c *Client) close() {
	c.once.Do(func() {
		c.mutex.Lock()
		defer c.mutex.Unlock()
		c.isClosed = true
		logrus.Debugf("客户下线[%v]", c)
		if c.uniqueCode != "" {
			tcpServer.clientMap.Delete(c.uniqueCode)
			dao.OnlineMap.Delete(c.uniqueCode)
		}
		if c.conn != nil {
			_ = c.conn.Close()
		}
		close(c.closeCh)
		close(c.inCh)
		close(c.outCh)
	})
}

func (c *Client) IsClosed() bool {
	return c.isClosed
}
